package com.bosssoft.cloud.wechat.infrastructure.util;

import cn.hutool.json.XML;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bosssoft.cloud.app.domain.AppCheckRecord;
import com.bosssoft.cloud.tools.utils.PngUtils;
import com.bosssoft.cloud.wechat.infrastructure.annotation.XmlRoot;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * XML解析工具类
 *
 * @author wcy create on 2021/5/26 14:29 下午
 */
@Slf4j
public class XmlUtil {

    @SneakyThrows
    public static <T> T getConvert(String pngBase64, Class<T> tClass) {
        // 从png的base64中解析出xml字符串
        String xml = new String(
                PngUtils.pngExtractXml(Base64.decodeBase64(pngBase64.getBytes())),
                StandardCharsets.UTF_8
        );
        // 获得xml字符串解析后的对象
        return getBean(xml, tClass);
    }

    /**
     * 将xml解析填充到o
     * （也可map，但用类限制更好）
     *
     * @param xml xml字符串
     * @param o   填充类
     * @param <T> T
     */
    public static <T> void fill(String xml, T o) {
        if (o instanceof AppCheckRecord) {
            fillRecord(xml, (AppCheckRecord) o);
        }
    }

    /**
     * 获得xml解析后的类
     *
     * @param xml   xml字符串
     * @param clazz class
     * @param <T>   T
     * @return 解析后的类
     */
    @SneakyThrows
    public static <T> T getBean(String xml, Class<T> clazz) {
        T target = clazz.newInstance();
        fill(xml, target);
        return target;
    }

    /**
     * 专为AppCheckRecord做的解析类
     * TODO 换成已有的方法
     *
     * @param xml    xml字符串
     * @param record 要解析填充的类
     */
    public static void fillRecord(String xml, AppCheckRecord record) {
        JSONObject rootJson = JSON.parseObject(JSON.toJSONString(XML.toJSONObject(xml)));
        try {
            JSONObject Main = rootJson.getJSONObject("EInvoice").getJSONObject("EInvoiceData").getJSONObject("Main");
            record.setEInvoiceName(Main.getString("EInvoiceName"));
            record.setEInvoiceCode(Main.getString("EInvoiceCode"));
            record.setEInvoiceNumber(Main.getString("EInvoiceNumber"));
            record.setRandomNumber(Main.getString("RandomNumber"));
            record.setTotalAmount(Main.getBigDecimal("TotalAmount"));
            record.setIssueDate(Main.getString("IssueDate"));
            record.setIssueTime(Main.getString("IssueTime"));
            JSONObject InvoicingParty = Main.getJSONObject("InvoicingParty");
            record.setInvoicingPartyName(InvoicingParty.getString("InvoicingPartyName"));
            record.setInvoicingPartyCode(InvoicingParty.getString("InvoicingPartyCode"));
            JSONObject PayerParty = Main.getJSONObject("PayerParty");
            record.setPayerPartyName(PayerParty.getString("PayerPartyName"));
            record.setPayerPartyCode(PayerParty.getString("PayerPartyCode"));
            record.setPayerPartyType(PayerParty.getInteger("PayerPartyType"));
        } catch (NullPointerException e) {
            log.error("xml获取{}对象异常:", AppCheckRecord.class.getSimpleName(), e);
        }
    }

    // 解决 CDATA 问题
    private static final Pattern pattern = Pattern.compile("[<>&\"',]");

    /**
     * obj转xmlStr
     *
     * @param obj obj
     * @return xmlStr
     */
    public static String toXml(Object obj) {
        // obj的jsonStr
        String jsonStr = JSON.toJSONString(obj);
        if (obj.getClass().isAnnotationPresent(XmlRoot.class)) {
            Class<?> clazz = obj.getClass();
            XmlRoot anno = clazz.getAnnotation(XmlRoot.class);
            jsonStr = "{\"" + anno.value() + "\":" + jsonStr + "}";
        }
        return jsonStr2Xml(jsonStr);
    }

    /**
     * jsonStr2Xml
     *
     * @param json jsonStr
     * @return xmlStr
     */
    public static String jsonStr2Xml(String json) {
        try {
            StringBuffer buffer = new StringBuffer();
            JSONObject jo = JSON.parseObject(json);
            return json2XmlStr(jo, buffer);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }


    /**
     * Json to xmlStr string
     *
     * @param jObj   the j obj
     * @param buffer the buffer
     * @return the string
     */
    public static String json2XmlStr(JSONObject jObj, StringBuffer buffer) {
        Set<Map.Entry<String, Object>> se = jObj.entrySet();
        // 遍历和分析JSONObject的每个节点
        for (Map.Entry<String, Object> en : se) {
            switch (en.getValue().getClass().getName()) {
                case "com.alibaba.fastjson.JSONObject":
                    // JSONObject
                    buffer.append("<").append(en.getKey()).append(">");
                    JSONObject jo = jObj.getJSONObject(en.getKey());
                    json2XmlStr(jo, buffer);
                    buffer.append("</").append(en.getKey()).append(">");
                    break;
                case "com.alibaba.fastjson.JSONArray":
                    // JSONArray
                    JSONArray jarray = jObj.getJSONArray(en.getKey());
                    for (int i = 0; i < jarray.size(); i++) {
                        buffer.append("<").append(en.getKey()).append(">");
                        JSONObject jsonobject = jarray.getJSONObject(i);
                        json2XmlStr(jsonobject, buffer);
                        buffer.append("</").append(en.getKey()).append(">");
                    }
                    break;
                case "java.lang.String":
                    // String
                    buffer.append("<").append(en.getKey()).append(">").append(escape((String) en.getValue()));
                    buffer.append("</").append(en.getKey()).append(">");
                    break;
            }
        }
        return buffer.toString();
    }

    public static <T> T toObj(String xmlStr, Class<T> clazz) {
        if (clazz.isAnnotationPresent(XmlRoot.class)) {
            XmlRoot anno = clazz.getAnnotation(XmlRoot.class);
            String rootVal = anno.value();
            // 找到xmlStr中符合class条件的这段
            int fIdx = xmlStr.indexOf("<" + rootVal + ">") + rootVal.length() + 2;
            int lIdx = xmlStr.lastIndexOf("</" + rootVal + ">");
            // 截取这段除去根元素后的部分
            xmlStr = xmlStr.substring(fIdx, lIdx);
        }
        JSONObject jo = toJson(xmlStr);
        return jo.toJavaObject(clazz);
    }

    /**
     * xmlStr转jsonObj
     *
     * @param xmlStr xmlStr
     * @return jsonObj
     */
    @SneakyThrows
    public static JSONObject toJson(String xmlStr) {
        xmlStr = updateXml(xmlStr);
        // xmlStr格式化后转document element
        Document doc = DocumentHelper.parseText(xmlStr);
        JSONObject json = new JSONObject();
        // 转json
        dom4j2Json(doc.getRootElement(), json);
        return json;
    }

    /**
     * toMap
     *
     * @param xmlStr xmlStr
     * @return map
     */
    public static Map<String, Object> toMap(String xmlStr) {
        String jsonStr = toJsonString(xmlStr);
        Map<String, Object> xmlMap = JSON.parseObject(jsonStr, LinkedHashMap.class);
        System.out.println(xmlMap);
        return xmlMap;
    }

    /**
     * xmlStr转jsonStr
     *
     * @param xmlStr xmlStr
     * @return jsonStr
     */
    public static String toJsonString(String xmlStr) {
        JSONObject jsonObject = toJson(xmlStr);
        return JSON.toJSONString(jsonObject);
    }

    /**
     * xml(element)转jsonObj
     *
     * @param element doc.getRootElement()
     * @param json    转后的jsonObj
     */
    private static void dom4j2Json(Element element, JSONObject json) {
        // 如果是属性
        for (Object o : element.attributes()) {
            Attribute attr = (Attribute) o;
            if (!isEmpty(attr.getValue())) {
                json.put("@" + attr.getName(), attr.getValue());
            }
        }
        List<Element> chdEl = element.elements();
        if (chdEl.isEmpty() && !isEmpty(element.getText())) { // 如果没有子元素,只有一个值
            json.put(element.getName(), element.getText());
        }

        for (Element e : chdEl) { // 有子元素
            if (!e.elements().isEmpty()) { // 子元素也有子元素
                JSONObject chdjson = new JSONObject();
                dom4j2Json(e, chdjson);
                Object o = json.get(e.getName());
                if (o != null) {
                    JSONArray jsona = null;
                    if (o instanceof JSONObject) { // 如果此元素已存在,则转为jsonArray
                        JSONObject jsono = (JSONObject) o;
                        json.remove(e.getName());
                        jsona = new JSONArray();
                        jsona.add(jsono);
                        jsona.add(chdjson);
                    }
                    if (o instanceof JSONArray) {
                        jsona = (JSONArray) o;
                        jsona.add(chdjson);
                    }
                    json.put(e.getName(), jsona);
                } else {
                    if (!chdjson.isEmpty()) {
                        json.put(e.getName(), chdjson);
                    }
                }
            } else { // 子元素没有子元素
                for (Object o : element.attributes()) {
                    Attribute attr = (Attribute) o;
                    if (!isEmpty(attr.getValue())) {
                        json.put("@" + attr.getName(), attr.getValue());
                    }
                }
                if (!e.getText().isEmpty()) {
                    json.put(e.getName(), e.getText());
                }
            }
        }
    }

    /**
     * 有特殊字符时，转为CDATA
     */
    private static String escape(String string) {
        return pattern.matcher(string).find() ? "<![CDATA[" + string + "]]>" : string;
    }

    /**
     * xmlStr格式化成Document str
     *
     * @param xmlStr xmlStr
     * @return 格式化后DocumentHelper可以识别的str
     */
    private static String updateXml(String xmlStr) {
        xmlStr = xmlStr.trim();
        if (StringUtils.isBlank(xmlStr)) {
            return xmlStr;
        }

        // 过滤非法字符
        xmlStr = xmlStr.replaceAll("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]", "");

        StringBuilder xmlSb = new StringBuilder(xmlStr);
        if (!xmlStr.startsWith("<?")) {
            xmlSb.insert(0, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
        }

        int idx = xmlSb.indexOf("?>") + 2;
        xmlSb.insert(idx, "<root_chinadaas>").append("</root_chinadaas>");
        return xmlSb.toString();
    }

    public static boolean isEmpty(String str) {
        return str == null || str.trim().isEmpty() || "null".equals(str);
    }

}
